/*++

ISAPIClipSQLInjection

Abstract:

    ISAPI extension to prevent SQL Injection by clippping SQL Injection
	patterns in post variables contents

Author:

	Rodney Viana (https://blogs.msdn.com/rodneyviana)

Resources:
    Using templates from Windows 2003 SDK

Acknowledgements:
    Special thanks to Jeff Sanders from Microsoft
--*/



#include <IsapiTools.h>
#include "SQLCleanUp.h"
#include "FileLog.h"
#include "ISAPISettings.h"
#include <stdlib.h>

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>


#define MODULE_NAME             "ISAPIClipSQLInjection"
#define MAX_CONFIG_LINE         1024
#define DEFAULT_ALLOW_UNMAPPED  1


#define REDIRECT_ENTITY \
    "<head><title>Document Moved</title></head>\r\n" \
    "<body><h1>Object Moved</h1>This document may "  \
    "be found <a HREF=\"%s\">here</a></body>"


ISAPI_STRING    g_strIniFile;
FileLog fileLog; 
CISAPISettings settings;


VOID
WINAPI
HandleCompletion(
    EXTENSION_CONTROL_BLOCK *   pecb,
    VOID *                      pContext,
    DWORD                       cbIo,
    DWORD                       dwError
    );

VOID
TrimCommentAndTrailingWhitespace(
    CHAR *  szString
    );

VOID
ClearSQLInjection(
	CHAR * szString
	);
				  

BOOL
GetMapping(
    CHAR *          szUrl,
    ISAPI_STRING *  pstrMapping
    );

DWORD
DoCustomError(
    EXTENSION_CONTROL_BLOCK *   pecb,
    CHAR *                      szConfigLine
    );

BOOL
SendRedirectResponse(
    EXTENSION_CONTROL_BLOCK *   pecb,
    CHAR *                      szLocation
    );

BOOL
WINAPI
GetExtensionVersion(
    HSE_VERSION_INFO *  pVer
    )
/*++

Purpose:

    Entry point for ISAPI extension initialization.  Required.

    This function will be called exactly once in the lifetime
    of the extension, at initialization time.  This function is
    a useful point to do complex initialization tasks.  This
    function is called outside of the system loader lock, so
    it does not have the same restrictions as DllMain.

Arguments:

    pVer - points to extension version info structure 

Returns:

    TRUE on success, FALSE on failure.
    
    Note that in the case of FALSE, IIS will call GetLastError()
    and return an error page to the client.  The entity body of
    the page may be textual if the FormatMessage API can resolve
    it, or it may just be an error number if FormatMessage can't
    resolve it.

--*/
{
    ISAPI_EXTENSION Extension;
    //
    // Tell IIS which version of ISAPI this extension was built from.
    //

    pVer->dwExtensionVersion = MAKELONG( HSE_VERSION_MINOR,
                                         HSE_VERSION_MAJOR);

    //
    // Report the description string for this extension.  Ensure
    // that it does not exceed the maximum length and is NULL
    // terminated.
    //

    strncpy( pVer->lpszExtensionDesc,
             "ISAPIClipSQLInjection", 
             HSE_MAX_EXT_DLL_NAME_LEN );

    pVer->lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN-1] = '\0';

	// Get Settings
	ISAPISETTINGS bulkSettings;

	bulkSettings = settings.GetBulkSettings();	
	if(!bulkSettings.IsLogEnabled)
		bulkSettings.level = None;
	
	fileLog.SetMinLogLevel(bulkSettings.level);

	fileLog.SetFileBaseName("IIS6SQLINJECTION");
	if(bulkSettings.LogFolder.empty())
		bulkSettings.LogFolder = "c:\\";
	fileLog.SetFolder(bulkSettings.LogFolder);
	fileLog.WriteLog(Information, "", "", "", "", "The Filter has started");
	fileLog.NewId();
    //
    // Get the INI filename
    //

    InitializeIsapiTools( MODULE_NAME );
    if ( Extension.Initialize( MODULE_NAME ) == FALSE )
    {
        goto Failed;
    }
    //
    // Return TRUE if we successfully initialized.
    //

    return TRUE;

Failed:

    WriteDebug( "GetExtensionVersion failed. Error %d.\r\n",
                GetLastError() );

    return FALSE;
}

string Upper(string& myString)
{
   for(int i=0;i<myString.length();i++)
   {
      myString[i] = toupper(myString[i]);
   }
   return myString;//return the converted string
}

DWORD
WINAPI
HttpExtensionProc(
    EXTENSION_CONTROL_BLOCK *   pecb
    )
/*++

Purpose:

    Entry point for ISAPI requests.  Required.

    This function is called once per request.  The EXTENSION_CONTROL_BLOCK
    passed in contains information and function pointers sufficient to
    interact with the server during this processing.

    Note that, once HttpExtensionProc is called, it is the responsibility
    of the extension to send back any data that the client should receive.
    IIS will not automatically generate any response, even in the case of
    HSE_STATUS_ERROR once the request has progressed to the point where
    this function is called.

    Also note that you should not make any blocking calls or do long-running
    operations within this function.  To allow efficient scaling of your
    server, you should consider creating a pool of private threads to handle
    the requests.  In that case, the implementation of this function would
    be just to add the pecb to a queue and return HSE_STATUS_PENDING.  The
    threads in the pool would be responsible for dequeing a pecb and calling
    HSE_REQ_DONE_WITH_SESSION when they are finished processing it.

Arguments:

    pecb - pointer to the extenstion control block 

Returns:

    HSE_STATUS_SUCCESS - Indicates successful request.
    HSE_STATUS_ERROR   - Indicates failed request.
    HSE_STATUS_PENDING - Indicates that processing of the request
                         should continue after this function returns.
                         It HSE_STATUS_PENDING is used, it is necessary
                         that HSE_REQ_DONE_WITH_SESSION is called
                         exactly once when processing is completed for
                         the request.

--*/
{
	LPHSE_EXEC_URL_INFO pExecUrlInfo = NULL;

	ISAPI_REQUEST       Request( pecb );
	ISAPI_BUFFER		aBuffer;
	BOOL				ignorePacket = false;
	string				formMethod;
	string				path;
	ISAPI_STRING		ip;
	ISAPI_STRING		originalUrl;
	ISAPI_STRING		strUrl;

	//  = 2005;
	pExecUrlInfo = new HSE_EXEC_URL_INFO;
	ZeroMemory(pExecUrlInfo, sizeof(HSE_EXEC_URL_INFO));

	// Make sure we have read all the body!

	if (!Request.ReadAllEntity( &aBuffer ) )
	{
		goto Failed;
	}

	Request.GetServerVariable("REMOTE_ADDR", &ip);
	Request.GetServerVariable("HTTP_URL", &originalUrl);

	if ( Request.GetServerVariable( "SCRIPT_NAME",
				&strUrl ) == FALSE )
	{
				goto Failed;
	}
	// if we have any data
	pExecUrlInfo->dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR;


	// this is where we check if we should ignore the call altogether or not
	formMethod.assign(pecb->lpszMethod);
	if(Upper(formMethod) != "POST" &&
		Upper(formMethod) != "GET")
	{
		
		fileLog.WriteLog(Information, formMethod, ip.QueryStr(), "", originalUrl.QueryStr(), "Request not filtered as it is neither GET or POST");  
		fileLog.NewId();
		ignorePacket = true;
	}
	
	if(!ignorePacket && settings.IgnoreThisFile(strUrl.QueryStr()))
	{
		fileLog.WriteLog(Warning, formMethod, ip.QueryStr(), "", strUrl.QueryStr(), "Not filtered. An explicit exclusion to this file");  
		fileLog.NewId();
		ignorePacket = true;
	}
	
	path.assign(pecb->lpszPathInfo);

	if(!ignorePacket)
	{
		if (aBuffer.QueryDataSize())
		{
			string body;
			//ISAPI_STRING query;
			CSQLCleanUp cleanUp;

			body.assign((char*)aBuffer.QueryPtr(), aBuffer.QueryDataSize());
			fileLog.WriteLog(Information, formMethod, ip.QueryStr(), body, originalUrl.QueryStr(), "Original Input Body");

			cleanUp.CleanURLVar(body);
			body = cleanUp.StrippedURL;


			pExecUrlInfo->pEntity = new HSE_EXEC_URL_ENTITY_INFO();

			pExecUrlInfo->pEntity->cbAvailable = body.size();
			//  = 2007;

			pExecUrlInfo->pEntity->lpbData = new DWORD[pExecUrlInfo->pEntity->cbAvailable]; 
			CopyMemory(pExecUrlInfo->pEntity->lpbData, body.c_str(),body.size());
			LOGLEVEL level = Information;
			if(cleanUp.IsUnsafeSequence())
				level = Warning;
			fileLog.WriteLog(level, formMethod, ip.QueryStr(), body, "", "Transformed Body");
			fileLog.NewId();
		}
		// Now it always change the query string
		// case of getting string
		if(pecb->lpszQueryString[0] != '\0')
		{

			fileLog.WriteLog(Information, formMethod, ip.QueryStr(), "", originalUrl.QueryStr(), "Original Get String");

			string query(pecb->lpszQueryString);
			CSQLCleanUp cleanUp;

			cleanUp.CleanURLVar(query);
			strUrl.Append("?", 1);
			strUrl.Append((char*)cleanUp.StrippedURL.c_str(), cleanUp.StrippedURL.size());
			//CopyMemory(pExecUrlInfo->pszUrl, query.
			//  = 2008;

			pExecUrlInfo->pszUrl = new char[strUrl.QueryBufferSize()+1];
			ZeroMemory(pExecUrlInfo->pszUrl, strUrl.QueryBufferSize()+1);
			CopyMemory(pExecUrlInfo->pszUrl, strUrl.QueryStr(), strUrl.QueryBufferSize());
			LOGLEVEL level = Information;
			if(cleanUp.IsUnsafeSequence())
				level = Warning;
			fileLog.WriteLog(level, formMethod, ip.QueryStr(), "", strUrl.QueryStr(), "Transformed Get String");
			fileLog.NewId();

		}
	}
	if ( pecb->ServerSupportFunction( pecb->ConnID,
		HSE_REQ_IO_COMPLETION,
		HandleCompletion,
		NULL,
		(DWORD *)(VOID *)pExecUrlInfo ) == FALSE )
	{
		goto Failed;
	}


	if ( pecb->ServerSupportFunction( pecb->ConnID,
		HSE_REQ_EXEC_URL,
		pExecUrlInfo,
		NULL,
		NULL ) == TRUE )
	{
		return HSE_STATUS_PENDING;
	}



	//
	// Fall through to error handler
	//

Failed:
	if (pExecUrlInfo)
	{
		if (pExecUrlInfo->pEntity)
		{
			if (pExecUrlInfo->pEntity->lpbData)
				delete pExecUrlInfo->pEntity->lpbData;
			delete pExecUrlInfo->pEntity;

		}
		if(pExecUrlInfo->pszUrl)
		{
			delete[] pExecUrlInfo->pszUrl;
		}
		delete pExecUrlInfo;
	}
	WriteDebug( "HttpExtensionProc failed.  Error %d.\r\n",
		GetLastError() );
	fileLog.WriteLog(Error, "", "", "", "", "Fatal Error");
	fileLog.NewId();
	return SyncSendGenericServerError( pecb );
}

VOID
WINAPI
HandleCompletion(
    EXTENSION_CONTROL_BLOCK *   pecb,
    VOID *                      pContext,
    DWORD                       cbIo,
    DWORD                       dwError
    )
{
    HSE_EXEC_URL_STATUS ExecUrlStatus;

    LPHSE_EXEC_URL_INFO pExecUrlInfo =(LPHSE_EXEC_URL_INFO)pContext;
	BOOL                fIsExecCompletion = FALSE;
	
    BOOL                fResult = TRUE;
	if (pExecUrlInfo != NULL)
	{
		fIsExecCompletion = TRUE;
	}
	
	
    //
    // If this is not an EXEC_URL completion, then we can just
    // skip to calling DONE_WITH_SESSION.  Otherwise, we need
    // to check the result of the child.
    //

    if ( !fIsExecCompletion )
    {
        goto Done;
    }

    fResult = pecb->ServerSupportFunction( pecb->ConnID,
                                           HSE_REQ_GET_EXEC_URL_STATUS,
                                           &ExecUrlStatus,
                                           NULL,
                                           NULL );

    if ( !fResult )
    {
        goto Done;
    }

        //
        // Just set the ECB's status member to match the child's
        // result
        //

        pecb->dwHttpStatusCode = ExecUrlStatus.uHttpStatusCode;


Done:

    if ( !fResult )
    {
        WriteDebug( "Error processing I/O completion.  Error %d.\r\n",
                    GetLastError() );
    }

    //
    // Notify IIS that we are done with the request
    //

    pecb->ServerSupportFunction( pecb->ConnID,
                                 HSE_REQ_DONE_WITH_SESSION,
                                 NULL,
                                 NULL,
                                 NULL );
	if (pExecUrlInfo)
	{
		if (pExecUrlInfo->pEntity)
		{
			if (pExecUrlInfo->pEntity->lpbData)
			{
				delete pExecUrlInfo->pEntity->lpbData;
				pExecUrlInfo->pEntity->lpbData = NULL;
			}
			if(pExecUrlInfo->pszUrl)
			{
				delete[] pExecUrlInfo->pszUrl;
			}

			delete pExecUrlInfo->pEntity;
			pExecUrlInfo->pEntity = NULL;
		}
		delete pExecUrlInfo;
		pExecUrlInfo = NULL;
	}

}
DWORD
DoCustomError(
    EXTENSION_CONTROL_BLOCK *   pecb,
    CHAR *                      szConfigLine
    )
{
    HSE_CUSTOM_ERROR_INFO   CustomError;
    ISAPI_REQUEST           Request( pecb );
    ISAPI_STRING            strStatus;
    ISAPI_STRING            strAlternateEntity;
    USHORT                  uSubStatus;
    CHAR *                  pCursor;

    //
    // We expect the config line to be in the
    // form:
    //
    //   500,13,Server Too Busy
    //

    if ( strStatus.Copy( szConfigLine ) == FALSE )
    {
        goto Failed;
    }

    pCursor = strchr( strStatus.QueryStr(), ',' );

    if ( !pCursor )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        
        goto Failed;
    }

    *pCursor = '\0';

    strStatus.CalcLen();
    
    pCursor++;

    uSubStatus = atoi( pCursor );

    pCursor = strchr( pCursor, ',' );

    if ( !pCursor )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        
        goto Failed;
    }

    if ( strAlternateEntity.Copy( pCursor+1 ) == FALSE )
    {
        goto Failed;
    }

    //
    // Set up for an asynchronous call to HSE_REQ_SEND_CUSTOM_ERROR
    //

    if ( strStatus.Append( " " ) == FALSE ||
         strStatus.Append( strAlternateEntity.QueryStr() ) == FALSE )
    {
        goto Failed;
    }

    CustomError.pszStatus = strStatus.QueryStr();
    CustomError.uHttpSubError = uSubStatus;
    CustomError.fAsync = TRUE;

    if ( pecb->ServerSupportFunction( pecb->ConnID,
                                      HSE_REQ_IO_COMPLETION,
                                      &HandleCompletion,
                                      NULL,
                                      NULL ) == FALSE )
    {
        goto Failed;
    }

    if ( pecb->ServerSupportFunction( pecb->ConnID,
                                      HSE_REQ_SEND_CUSTOM_ERROR,
                                      &CustomError,
                                      NULL,
                                      NULL ) == TRUE )
    {
        //
        // The async call succeeded.  We need to return STATUS_PENDING
        // without doing anything else to the ECB.
        //

        return HSE_STATUS_PENDING;
    }

    //
    // If we get here, then the asynchronous call failed. We'll go
    // ahead and send back a synchronous error manually.
    //

    if ( Request.SyncWriteCompleteResponse( strStatus.QueryStr(),
                                            "Content-Type: text/plain\r\n\r\n",
                                            strAlternateEntity.QueryStr() ) == FALSE )
    {
        goto Failed;
    }

    return HSE_STATUS_SUCCESS;

Failed:

    WriteDebug( "DoCustomError failed.  Error %d.\r\n",
                GetLastError() );

    return SyncSendGenericServerError( pecb );
}

BOOL WINAPI TerminateExtension(DWORD dwFlags)
{
	BOOL abRet = false;
	
	if (dwFlags & HSE_TERM_ADVISORY_UNLOAD)
	{
		// Can be uloaded!
		abRet=true;
	}
	
	// dump leaks to output window in debugger
	_CrtDumpMemoryLeaks();

	return abRet;
}

